#! /usr/bin/env bash
set -e
#set -x

TOP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"

ARCH="$(uname -m)"
SUPPORTED_ARCHS=" x86_64 aarch64 "

AVAILABLE_COMMANDS="build clean -h --help"

SUPPORTED_DEVICE_CONNECT=(rsu cloud traffic_light ins radar lidar camera)
SUPPORTED_TEST_TOOL_DEVICE=(traffic_light camera lidar ins)
SUPPORTED_DEVICE_SERVICE=(traffic_light rsu cloud)
PROTOCOL_STACK_COMPONENT=(traffic_light_adapter v2x_codec)
ARI_RUNTIME_TEST_PROGRAM=(runtime_ut runtime_component_demo runtime_node_demo runtime_listener_demo runtime_talker_demo)

OUT_ROOT="${TOP_DIR}/output"
OUT_3RD="${OUT_ROOT}/3rd"
OUT_TEST="${OUT_ROOT}/test"
OUT_TOOL="${OUT_ROOT}/tool"
OUT_PROTO="${OUT_ROOT}/protobuf"

device_connect_path="base/device_connect"
device_service_path="middleware/device_service"
protocol_path="middleware/protocol"
airruntime_path="middleware/runtime"

function check_architecture_support() {
    if [[ "${SUPPORTED_ARCHS}" != *" ${ARCH} "* ]]; then
        echo "Unsupported CPU arch: ${ARCH}. Currently, AIROS only" \
            "supports running on the following CPU archs:"
        echo "${TAB}${SUPPORTED_ARCHS}"
        exit 1
    fi
}

function check_platform_support() {
    local platform="$(uname -s)"
    if [[ "${platform}" != "Linux" ]]; then
        echo "Unsupported platform: ${platform}."
        echo "${TAB}AIROS is expected to run on Linux systems (E.g., Debian/Ubuntu)."
        exit 1
    fi
}

function check_minimal_memory_requirement() {
    local minimal_mem_gb="2.0"
    local actual_mem_gb="$(free -m | awk '/Mem:/ {printf("%0.2f", $2 / 1024.0)}')"
    if (($(echo "$actual_mem_gb < $minimal_mem_gb" | bc -l))); then
        echo "System memory [${actual_mem_gb}G] is lower than the minimum required" \
            "[${minimal_mem_gb}G]. AIROS build could fail."
    fi
}


function env_setup() {
    check_architecture_support
    check_platform_support
    check_minimal_memory_requirement   
}


function release_protobuf {
    bazel build @com_google_protobuf//:protoc
    bazel build @com_google_protobuf//:protobuf
    bazel build @com_google_protobuf//:protobuf_lite

    local proto_buf="${TOP_DIR}/bazel-bin/external/com_google_protobuf"
    local proto_out=${OUT_PROTO}
    [ -d "${proto_out}" ] || mkdir -p ${proto_out}
    [ -e "${proto_buf}/protoc" ] && cp -f ${proto_buf}/protoc ${proto_out}/
    [ -e "${proto_buf}/libprotobuf.so" ] && cp -f ${proto_buf}/libprotobuf.so ${proto_out}/
    [ -e "${proto_buf}/libprotobuf_lite.so" ] && cp -f ${proto_buf}/libprotobuf_lite.so ${proto_out}/
    [ -e "${TOP_DIR}/third_party/protobuf/.include" ] && mkdir -p ${proto_out}/include/google/protobuf && \
        cp -rf ${TOP_DIR}/third_party/protobuf/.include/* $mkdir -p ${proto_out}/include/google/protobuf/
}


function release_airosrt() {
    local bin_dir="${OUT_ROOT}/airosrt/bin"
    local conf_dir="${OUT_ROOT}/airosrt/cyber"
    mkdir -p ${bin_dir}
    mkdir -p ${conf_dir}
    [ -d "/opt/airosrt/bin" ] && \
        cp -rf /opt/airosrt/bin/* ${bin_dir}/

    [ -d "/opt/airosrt/cyber" ] && \
        cp -rf /opt/airosrt/cyber/* ${conf_dir}/
    touch ${conf_dir}/../__init__.py
}


##############################################################################################################

function build_base_device_connect_pb() {
    for device in ${SUPPORTED_DEVICE_CONNECT[@]}
    do
        if [ -f "${TOP_DIR}/${device_connect_path}/proto/${device}_data.proto" ]; then
            bazel build "//${device_connect_path}/proto:${device}_data_cc_pb"
        fi
    done
}

function release_base_device_connect_pb() {
    for device in ${SUPPORTED_DEVICE_CONNECT[@]}
    do
        local tmp_out="${OUT_ROOT}/${device_connect_path}/${device}"
        local tmp_out_inc="${tmp_out}/include"
        [ -d "${tmp_out_inc}" ] || mkdir -p ${tmp_out_inc}
        local tmp_out_lib="${tmp_out}/lib"
        [ -d "${tmp_out_lib}" ] || mkdir -p ${tmp_out_lib}
        local tmp_out_proto="${tmp_out}/proto"
        [ -d "${tmp_out_proto}" ] || mkdir -p ${tmp_out_proto}

        if [ -f "${TOP_DIR}/${device_connect_path}/proto/${device}_data.proto" ]; then
            cp -f ${TOP_DIR}/bazel-bin/${device_connect_path}/proto/${device}_data.pb.h ${tmp_out_inc}
            cp -f ${TOP_DIR}/bazel-bin/${device_connect_path}/proto/lib${device}_data_pb.so ${tmp_out_lib}
            cp -f ${TOP_DIR}/${device_connect_path}/proto/${device}_data.proto   ${tmp_out_proto}
        fi
    done
}


function build_base_device_connect() {
    for device in ${SUPPORTED_DEVICE_CONNECT[@]}
    do
        bazel build "//${device_connect_path}/${device}:${device}_device"
    done
}

function release_base_device_connect() {
    for device in ${SUPPORTED_DEVICE_CONNECT[@]}
    do
        local tmp_out="${OUT_ROOT}/${device_connect_path}/${device}"
        local tmp_out_inc="${tmp_out}/include"
        [ -d "${tmp_out_inc}" ] || mkdir -p ${tmp_out_inc}
        local tmp_out_lib="${tmp_out}/lib"
        [ -d "${tmp_out_lib}" ] || mkdir -p ${tmp_out_lib}

        cp -f ${TOP_DIR}/bazel-bin/${device_connect_path}/${device}/lib${device}_device.so ${tmp_out_lib}
        cp -f ${TOP_DIR}/${device_connect_path}/${device}/device_base.h ${tmp_out_inc}
        cp -f ${TOP_DIR}/${device_connect_path}/${device}/device_factory.h ${tmp_out_inc}

        depends_lib="{TOP_DIR}/${device_connect_path}/${device}/lib/"
        if [ -d ${depends_lib} ]; then
            find ${depends_lib}/ -name "lib*.so*" | xargs -I {} cp -f {} ${tmp_out_lib}/
        fi
    done
}

function build_base_device_connect_ut() {
    for device in ${SUPPORTED_DEVICE_CONNECT[@]}
    do
        bazel build "//${device_connect_path}/${device}:${device}_ut"
    done
}

function release_base_device_connect_ut() {
    for device in ${SUPPORTED_DEVICE_CONNECT[@]}
    do
        local tmp_out="${OUT_ROOT}/${device_connect_path}/${device}"
        local tmp_out_ut="${tmp_out}/ut"
        [ -d "${tmp_out_ut}" ] || mkdir -p ${tmp_out_ut}
        cp -f ${TOP_DIR}/bazel-bin/${device_connect_path}/${device}/${device}_ut ${tmp_out_ut}
    done
}

function build_base_device_connect_tool() {
    for device in ${SUPPORTED_TEST_TOOL_DEVICE[@]}
    do
        bazel build "//${device_connect_path}/${device}:${device}_test_tool"
    done
}

function release_base_device_connect_tool() {
    for device in ${SUPPORTED_TEST_TOOL_DEVICE[@]}
    do
        local tmp_out="${OUT_ROOT}/${device_connect_path}/${device}"
        local tmp_out_test_tool="${tmp_out}/test_tool"
        [ -d "${tmp_out_test_tool}" ] || mkdir -p ${tmp_out_test_tool}
        cp -f ${TOP_DIR}/bazel-bin/${device_connect_path}/${device}/${device}_test_tool ${tmp_out_test_tool}
    done
}

function build_device_connect_func() {
    build_base_device_connect_pb
    build_base_device_connect
    build_base_device_connect_ut
    build_base_device_connect_tool
}


function release_base_device_connect_func() {
    release_base_device_connect_pb
    release_base_device_connect
    release_base_device_connect_ut
    release_base_device_connect_tool
}


function run_base_device_connect_ut() {
    for device in ${SUPPORTED_DEVICE_CONNECT[@]}
    do
        pushd ./bazel-bin/${device_connect_path}/${device} > /dev/null
            ./${device}_ut
        popd > /dev/null  
    done
    echo "base device connect ut done!"
}


##############################################################################################################

function build_middleware_device_service_pb() {

    for device in ${SUPPORTED_DEVICE_SERVICE[@]}
    do
        if [ -f "${TOP_DIR}/${device_service_path}/proto/${device}_config.proto" ]; then
            bazel build //${device_service_path}/proto:${device}_config_cc_pb 
        fi
    done
}


function release_middleware_device_service_pb() {
    for device in ${SUPPORTED_DEVICE_SERVICE[@]}
    do
        local tmp_out="${OUT_ROOT}/${device_service_path}/${device}"
        local tmp_out_inc="${tmp_out}/include"
        [ -d "${tmp_out_inc}" ] || mkdir -p ${tmp_out_inc}
        local tmp_out_lib="${tmp_out}/lib"
        [ -d "${tmp_out_lib}" ] || mkdir -p ${tmp_out_lib}
        local tmp_out_proto="${tmp_out}/proto"
        [ -d "${tmp_out_proto}" ] || mkdir -p ${tmp_out_proto}

        if [ -f "${TOP_DIR}/${device_connect_path}/proto/${device}_data.proto" ]; then
            cp -f ${TOP_DIR}/bazel-bin/${device_service_path}/proto/${device}_config.pb.h ${tmp_out_inc}
            cp -f ${TOP_DIR}/bazel-bin/${device_service_path}/proto/lib${device}_config_pb.so ${tmp_out_lib}
            cp -f ${TOP_DIR}/${device_service_path}/proto/${device}_config.proto   ${tmp_out_proto}
        fi
    done
}


function build_middleware_device_service() {
    for device in ${SUPPORTED_DEVICE_SERVICE[@]}
    do
        bazel build "//${device_service_path}/${device}:lib${device}_component.so"
    done
}

function release_middleware_device_service() {
    for device in ${SUPPORTED_DEVICE_SERVICE[@]}
    do
        local tmp_out="${OUT_ROOT}/${device_service_path}/${device}"
        # local tmp_out_inc="${tmp_out}/include"
        # [ -d "${tmp_out_inc}" ] || mkdir -p ${tmp_out_inc}
        local tmp_out_lib="${tmp_out}/lib"
        [ -d "${tmp_out_lib}" ] || mkdir -p ${tmp_out_lib}
        local so_path="${TOP_DIR}/bazel-bin/${device_service_path}/${device}/lib${device}_component.so"
        if [ -e "${so_path}" ];then 
            cp -f ${so_path} ${tmp_out_lib}
        fi
        local tmp_out_bin="${tmp_out}/bin"
        [ -d "${tmp_out_bin}" ] || mkdir -p ${tmp_out_bin}
        local bin_path="${TOP_DIR}/bazel-bin/${device_service_path}/${device}/${device}_component"
        if [ -e "${bin_path}" ];then 
            cp -f ${bin_path} ${tmp_out_bin}
        fi
        depends_lib="{TOP_DIR}/${device_service_path}/${device}/lib/"
        if [ -d ${depends_lib} ]; then
            find ${depends_lib}/ -name "lib*.so*" | xargs -I {} cp -f {} ${tmp_out_lib}/
        fi
    done
}

function build_middleware_device_service_ut() {
    bazel build "//${device_service_path}/traffic_light:traffic_light_service_ut"
}

function release_middleware_device_service_ut() {
    local tmp_out="${OUT_ROOT}/${device_service_path}/traffic_light"
    local tmp_out_ut="${tmp_out}/ut"
    [ -d "${tmp_out_ut}" ] || mkdir -p ${tmp_out_ut}
    cp -f ${TOP_DIR}/bazel-bin/${device_service_path}/traffic_light/traffic_light_service_ut ${tmp_out_ut}
    local tmp_cfg_dir="${TOP_DIR}/${device_service_path}/traffic_light/ut/testdata/ "
    [ -d ${tmp_cfg_dir} ] && cp -rf ${tmp_cfg_dir} ${tmp_out_ut}
}

function build_middleware_device_service_func() {
    build_middleware_device_service_pb
    build_middleware_device_service
    build_middleware_device_service_ut
}


function release_middleware_device_service_func() {
    release_middleware_device_service_pb
    release_middleware_device_service
    release_middleware_device_service_ut
}

##############################################################################################################

function build_middleware_protocol_pb() {
    
    for proto in ${protocol_path}/proto/*.proto;
    do
	bazel build //${protocol_path}/proto:$(basename ${proto%.*})_cc_pb
    done
}

function build_middleware_protocol() {
    for component in ${PROTOCOL_STACK_COMPONENT[@]}
    do
        bazel build "//${protocol_path}/${component}:lib${component}.so"
    done
}

function release_middleware_protocol_pb(){
    for component in ${PROTOCOL_STACK_COMPONENT[@]}
    do
        local tmp_out="${OUT_ROOT}/${protocol_path}/${component}"
        local tmp_out_inc="${tmp_out}/include"
        [ -d "${tmp_out_inc}" ] || mkdir -p ${tmp_out_inc}
        local tmp_out_lib="${tmp_out}/lib"
        [ -d "${tmp_out_lib}" ] || mkdir -p ${tmp_out_lib}
        local tmp_out_proto="${tmp_out}/proto"
        [ -d "${tmp_out_proto}" ] || mkdir -p ${tmp_out_proto}

	cp -f ${TOP_DIR}/bazel-bin/${protocol_path}/proto/*.pb.h ${tmp_out_inc}
	cp -f ${TOP_DIR}/bazel-bin/${protocol_path}/proto/*.so ${tmp_out_lib}
	cp -f ${TOP_DIR}/${protocol_path}/proto/*.proto ${tmp_out_proto}
    done
}

function release_middleware_protocol(){
    for component in ${PROTOCOL_STACK_COMPONENT[@]}
    do
        local tmp_out="${OUT_ROOT}/${protocol_path}/${component}"
        local tmp_out_lib="${tmp_out}/lib"
        [ -d "${tmp_out_lib}" ] || mkdir -p ${tmp_out_lib}
        local so_path="${TOP_DIR}/bazel-bin/${protocol_path}/${component}/lib${component}.so"
        if [ -e "${so_path}" ];then
            cp -f ${so_path} ${tmp_out_lib}
        fi
    done
}

function build_middleware_protocol_ut() {
    for component in ${PROTOCOL_STACK_COMPONENT[@]}
    do
        bazel build "//${protocol_path}/${component}:${component}_ut"
    done
}

function release_middleware_protocol_ut() {
    for component in ${PROTOCOL_STACK_COMPONENT[@]}
    do
        local tmp_out="${OUT_ROOT}/${protocol_path}/${component}"
        local tmp_out_ut="${tmp_out}/ut"
        [ -d "${tmp_out_ut}" ] || mkdir -p ${tmp_out_ut}
        cp -f ${TOP_DIR}/bazel-bin/${protocol_path}/${component}/${component}_ut ${tmp_out_ut}
        local tmp_cfg_dir="${TOP_DIR}/${protocol_path}/${component}/ut/testdata"
        [ -d ${tmp_cfg_dir} ] && cp -rf ${tmp_cfg_dir} ${tmp_out_ut}
    done
}

function build_middleware_protocol_func() {
    build_middleware_protocol_pb
    build_middleware_protocol
    build_middleware_protocol_ut
}

function release_middleware_protocol_func() {
    release_middleware_protocol_pb
    release_middleware_protocol
    release_middleware_protocol_ut
}

##############################################################################################################
function build_middleware_runtime_func() {
    for bin in ${ARI_RUNTIME_TEST_PROGRAM[@]}
    do
        bazel build "${airruntime_path}/${bin}"
    done
}

function release_middleware_runtime_func() {
    local tmp_out="${OUT_ROOT}/airosrt/test"
    for bin in ${ARI_RUNTIME_TEST_PROGRAM[@]}
    do
        [ -d "${tmp_out}" ] || mkdir -p ${tmp_out}
        cp -f ${TOP_DIR}/bazel-bin/${airruntime_path}/${bin} ${tmp_out}
    done
    cfg_dir="${TOP_DIR}/${airruntime_path}/demo"
    [ -d ${tmp_out} ] && cp -f ${cfg_dir}/test.flags ${tmp_out} && cp -f ${cfg_dir}/test.pt ${tmp_out}
}

function run_middleware_runtime_ut() {
    pushd ./bazel-bin/${airruntime_path} > /dev/null
        ./runtime_ut
    popd > /dev/null  
    echo "middleware runtime ut done!"
}
##############################################################################################################

function build_all() {
    release_protobuf
    release_airosrt

    # base/device_connect
    build_device_connect_func
    # middleware/device_service
    build_middleware_device_service_func
    # middleware/protocol
    build_middleware_protocol_func
    # middleware/runtime
    build_middleware_runtime_func
    echo "build all done!"
}


function install_all() {
    # base/device_connect
    release_base_device_connect_func
    # middleware/device_service
    release_middleware_device_service_func
    # middleware/protocol
    release_middleware_protocol_func
    # middleware/runtime
    release_middleware_runtime_func
    echo "install all done!"
}

function release_out {
    local out_dir=${OUT_3RD}
    [ -d "${out_dir}" ] || mkdir -p ${out_dir}

    for lib in $(ls /opt)
    do
        if [ -e "/opt/${lib}/lib" ]; then
            find /opt/${lib}/lib/ -name "lib*.so*" | xargs -I {} cp -f {} ${out_dir}/
        fi
    done

    local workspace_path=$(dirname $(readlink -f "$0"))
    local workspace_md5_val=`echo -n ${workspace_path} | md5sum | cut -d " " -f1`
    local bazel_cache_path="${HOME}/.cache/bazel/_bazel_${USER}"
    local bazel_cache_lib_path="${bazel_cache_path}/${workspace_md5_val}/execroot/airos/bazel-out/k8-fastbuild/bin/_solib_k8/"

    find ${bazel_cache_lib_path} -name "lib*.so*" | xargs -I {} cp -df {} ${out_dir}/
    echo "release done."
}


function clean_func() {
    local out_dir="${TOP_DIR}/output"
    bazel clean
    if [ -d $out_dir ]; then
        sudo rm -rf $out_dir
    fi
    echo "clean done!"
}

function run_ut() {
    pushd ${TOP_DIR} > /dev/null
        source setup.bash
    popd

    run_base_device_connect_ut
    run_middleware_runtime_ut
    echo "ut done!"
}



function main() {
    if [ "$#" -eq 0 ]; then
        exit 0
    fi

    env_setup

    local cmd="$1"
    shift
    case "${cmd}" in
        build)
            build_all
            install_all
            ;;
        test)
            build_all
            install_all
            release_out
            run_ut
            ;;
        clean)
            clean_func
            ;;
        release)
            build_all
            install_all
            release_out
            ;;
        *)
            ;;
    esac
}

main "$@"
